const CENTER = 'center';
const LEFT = 'left';
const TOP = 'top';
const BOTTOM = 'bottom';
const RIGHT = 'right';
const NONE = 'none';
const reNewline = /\r?\n/;
const MOVING = 'moving';
const SCALING = 'scaling';
const ROTATING = 'rotating';
const ROTATE = 'rotate';
const SKEWING = 'skewing';
const RESIZING = 'resizing';
const MODIFY_POLY = 'modifyPoly';
const MODIFY_PATH = 'modifyPath';
const CHANGED = 'changed';
const SCALE = 'scale';
const SCALE_X = 'scaleX';
const SCALE_Y = 'scaleY';
const SKEW_X = 'skewX';
const SKEW_Y = 'skewY';
const FILL = 'fill';
const STROKE = 'stroke';
const MODIFIED = 'modified';

import {
    ActiveSelection, classRegistry, FabricObject, InteractiveFabricObject, Intersection, ModifierKey, Point, TBBox, TOptionalModifierKey, TPointerEvent,
    config
} from "fabric";
import { EventDispatcher } from "../myThree/build/three.module";
import fabricUtils from "./fabricUtils";

import { sendPointToPlane } from "./planeChange";

import utils from "src/utils/utils";
import { FabricJSON } from "types/type";
import FabricImg from "./FabricImg";
import FabricText from "./FabricText";
import fontManage from "./fontManage";
import imgCache from "./imgCache";
import { Path as fabricPath, Polygon as fabricPolygon, Circle as fabricCircle, Rect as fabricRact } from 'fabric';
import { removeFromArray } from "../Designer/removeFromArray";
import { cloneDeep, isEqual } from "lodash";
import { toJS } from "mobx";

// 分辨率设置为1
config.devicePixelRatio = 1;

FabricObject.prototype.transparentCorners = false;
FabricObject.prototype.borderScaleFactor = 2;
//@ts-ignore
FabricObject.prototype.NUM_FRACTION_DIGITS = 6; // 小数位数
FabricObject.prototype.touchCornerSize = 100;

// 自定义的微信小程序专用 fabric.Canvas 
export default class FabricCanvas extends EventDispatcher {

    constructor() {
        super();
    }

    width = 1024;
    height = 1024;
    background?: `#${string}` = undefined;

    // 主画布
    canvas;
    //画布对象
    ctx;

    init(canvas) {
        this.canvas = canvas;
        canvas.width = 1024;
        canvas.height = 1024;
        this.ctx = this.canvas.getContext("2d");
    }

    rect;
    setRect(rect) {
        this.rect = rect;
    }

    getContext() {
        return this.ctx;
    }

    fire(eventName, option) {
        this.dispatchEvent({ type: eventName, ...option });
    }
    /**
     * 保存对当前正在进行的变换的数据结构的引用
     * @type
     * @private
     */
    viewportTransform = [1, 0, 0, 1, 0, 0];
    setViewportTransform(viewportTransform) {
        this.viewportTransform = viewportTransform;
    }
    _currentTransform: any = null;
    _isClick = false;
    _absolutePointer;
    _pointer;
    _target;
    _hoveredTargets: Array<FabricObject> = [];
    _offset = {
        left: 0,
        top: 0
    };
    // 当前选中的元素
    _activeObject;
    getActiveObject() {
        return this._activeObject;
    }
    getActiveObjects() {
        const active = this._activeObject;
        return fabricUtils.isActiveSelection(active)
            ? active.getObjects()
            : active
                ? [active]
                : [];
    }
    //子元素
    _objects: Array<FabricObject> = [];
    /**
     * 返回此实例的子对象数组
     * @param {...String} [types] 指定时，仅返回这些类型的对象
     * @return {Array}
     */
    getObjects(...types) {
        if (types.length === 0) {
            return [...this._objects];
        }
        return this._objects.filter((o: any) => o.isType(...types));
    }
    /**
     * 向集合中添加对象
     * 对象应为 FabricObject 的实例或继承自 FabricObject
     * @param {...FabricObject[]} objects 要添加的对象
     * @returns {number} 新数组长度
     */
    add(...objects: Array<any>) {
        objects.map(obj => {
            obj._set('canvas', this);
            obj.setCoords();
        });
        const size = this._objects.push(...objects);
        return size;
    }
    /**
     * 从集合中移除对象，然后渲染画布（如果 `renderOnAddRemove` 不是 `false`）
     * @param {...FabricObject[]} objects 要移除的对象
     * @returns {FabricObject[]} 移除的对象
     */
    remove(...objects: Array<FabricObject>) {
        const array = this._objects,
            removed: Array<FabricObject> = [];
        objects.forEach((object) => {
            const index = array.indexOf(object);
            // 仅在对象实际移除时调用 onObjectRemoved
            if (index !== -1) {
                array.splice(index, 1);
                removed.push(object);
                object._set('canvas', undefined);
            }
        });
        return removed;
    }

    // 设置为当前选中的元素
    setActiveObject(object, e?: any) {
        // 我们不能内联这个，因为 _setActiveObject 会改变 getActiveObjects 返回的内容
        const currentActives = this.getActiveObjects();
        const selected = this._setActiveObject(object, e);
        this._fireSelectionEvents(currentActives, e);
        object.setCoords();
        return selected;
    }

    /**
 * 这应该等同于 setActiveObject，但不触发任何事件。没有承诺保持这种方式。
 * 这是 setActiveObject 的功能部分。
 * @param {FabricObject} object 要设置为活动对象
 * @param {Event} [e] 事件（在触发 "object:selected" 时传递）
 * @return {Boolean} 如果对象已被选中，则返回 true
 */
    _setActiveObject(object, e) {
        const prevActiveObject = this._activeObject;
        if (prevActiveObject === object) {
            return false;
        }
        // 调用 this._discardActiveObject 后，this,_activeObject 可能未定义
        if (!this._discardActiveObject(e, object) && this._activeObject) {
            // 拒绝取消选择
            return false;
        }
        if (object.onSelect({ e })) {
            return false;
        }

        this._activeObject = object;

        if (fabricUtils.isActiveSelection(object) && prevActiveObject !== object) {
            object.set('canvas', this);
        }
        object.setCoords();

        return true;
    }

    getRetinaScaling() {
        return 1;
    }

    /**
 * 将对象或多个选中的对象移动到绘制对象堆栈的底部
  * @param {fabric.Object} object 要移动到底部的对象
  * @returns {boolean} 如果发生更改则返回 true
*/
    sendObjectToBack(object: FabricObject) {
        if (!object || object === this._objects[0]) {
            return false;
        }
        removeFromArray(this._objects, object);
        this._objects.unshift(object);
        // this._onStackOrderChanged(object);
        this.requestRenderAll();
        return true;
    }

    /**
      * 将对象或选择的所有对象
      * 移动到已绘制对象堆栈的顶部
      * @param {fabric.Object} object 要发送的对象
      * @returns {boolean} 如果发生更改则返回 true
     */
    bringObjectToFront(object: FabricObject) {
        if (!object || object === this._objects[this._objects.length - 1]) {
            return false;
        }
        removeFromArray(this._objects, object);
        this._objects.push(object);
        // this._onStackOrderChanged(object);
        this.requestRenderAll();
        return true;
    }

    /**
     * 将对象或选择向下移动到绘制对象堆栈中
     * 可选参数 `intersecting` 允许将对象移到第一个相交对象的后面。
     * 如果未找到相交，则堆栈中不会发生更改。
     * @param {fabric.Object} object 要移动的对象
     * @param {boolean} [intersecting] 如果为 `true`，将对象移到下一个较低的相交对象后面
     * @returns {boolean} 如果发生更改，则返回 true
     */
    sendObjectBackwards(object, intersecting = false) {
        console.log('sendObjectBackwards');

        if (!object) {
            return false;
        }
        const idx = this._objects.indexOf(object);
        if (idx !== 0) {
            // 如果对象不在堆栈底部
            const newIdx = this.findNewLowerIndex(object, idx, intersecting);
            fabricUtils.removeFromArray(this._objects, object);
            this._objects.splice(newIdx, 0, object);
            this.requestRenderAll();
            return true;
        }
        return false;
    }

    /**
     * 将对象或选择向上移动到绘制对象堆栈中
     * 可选参数 `intersecting` 允许将对象移到第一个相交对象的前面。
     * 如果未找到相交，则堆栈中不会发生更改。
     * @param {fabric.Object} object 要移动的对象
     * @param {boolean} [intersecting] 如果为 `true`，将对象移到下一个较高的相交对象前面
     * @returns {boolean} 如果发生更改，则返回 true
     */
    bringObjectForward(object, intersecting = false) {
        console.log('bringObjectForward');

        if (!object) {
            return false;
        }
        const idx = this._objects.indexOf(object);
        if (idx !== this._objects.length - 1) {
            // 如果对象不在堆栈顶部（数组的最后一项）
            const newIdx = this.findNewUpperIndex(object, idx, intersecting);
            fabricUtils.removeFromArray(this._objects, object);
            this._objects.splice(newIdx, 0, object);
            this.requestRenderAll();
            return true;
        }
        return false;
    }

    findNewLowerIndex(
        object,
        idx,
        intersecting,
    ) {
        let newIdx;

        if (intersecting) {
            newIdx = idx;
            // 向下遍历堆栈，寻找最近的相交对象
            for (let i = idx - 1; i >= 0; --i) {
                if (object.isOverlapping(this._objects[i])) {
                    newIdx = i;
                    break;
                }
            }
        } else {
            newIdx = idx - 1;
        }

        return newIdx;
    }

    findNewUpperIndex(
        object,
        idx,
        intersecting,
    ) {
        let newIdx;

        if (intersecting) {
            newIdx = idx;
            // 向上遍历堆栈，寻找最近的相交对象
            for (let i = idx + 1; i < this._objects.length; ++i) {
                if (object.isOverlapping(this._objects[i])) {
                    newIdx = i;
                    break;
                }
            }
        } else {
            newIdx = idx + 1;
        }

        return newIdx;
    }

    //上移一层
    upMovellayerout() {
        const activeObject = this.getActiveObject();
        if (activeObject) {
            this.bringObjectForward(activeObject);
        }
    }

    //下移一层
    downMovellayerout() {
        const activeObject = this.getActiveObject();
        if (activeObject) {
            this.sendObjectBackwards(activeObject);
        }
    }


    //移动到顶层
    moveLayeroutToTop() {
        const activeObject = this.getActiveObject();
        if (activeObject) {
            this.sendObjectToBack(activeObject);
        }
    }

    // 移动到底层
    moveLayeroutToBottom() {
        const activeObject = this.getActiveObject();
        if (activeObject) {
            this.bringObjectToFront(activeObject);
        }
    }

    //微调选中的对象
    moveActiveObject(left: number, top: number) {
        const activeObj = this.getActiveObject();
        if (activeObj) {
            activeObj.set({
                top: (activeObj.top || 0) + top,
                left: (activeObj.left || 0) + left,
            });
            this.renderAll();
        }
    }

    getPointer(e, fromViewport = false) {
        const bounds = this.rect;
        let pointer = fabricUtils.getPointer(e),
            boundsWidth = bounds.width || 0,
            boundsHeight = bounds.height || 0;

        if (!boundsWidth || !boundsHeight) {
            if (TOP in bounds && BOTTOM in bounds) {
                boundsHeight = Math.abs(bounds.top - bounds.bottom);
            }
            if (RIGHT in bounds && LEFT in bounds) {
                boundsWidth = Math.abs(bounds.right - bounds.left);
            }
        }

        // this.calcOffset();
        pointer.x = pointer.x - this._offset.left;
        pointer.y = pointer.y - this._offset.top;
        if (!fromViewport) {
            pointer = sendPointToPlane(pointer, undefined, this.viewportTransform);
        }

        const retinaScaling = this.getRetinaScaling();
        if (retinaScaling !== 1) {
            pointer.x /= retinaScaling;
            pointer.y /= retinaScaling;
        }

        // 如果边界不可用（即不可见），则不应用比例。
        const cssScale =
            boundsWidth === 0 || boundsHeight === 0 ?
                new Point(1, 1) :
                new Point(
                    this.width / boundsWidth,
                    this.height / boundsHeight,
                );

        return pointer.multiply(cssScale);
    }
    /**
     * 决定在 mouseup 和 mousedown 事件中是否应重绘画布。
     * @private
     * @param {FabricObject} target
     */
    _shouldRender(target) {
        const activeObject = this.getActiveObject();
        // 如果只有其中一个可用，或者它们都是但不同的对象
        // 这涵盖了：目标切换，从目标到无目标，目标选择
        // 使用键和鼠标的多重选择
        return (
            !!activeObject !== !!target ||
            (activeObject && target && activeObject !== target)
        );
    }
    mainTouchId?: 0 = undefined;
    /**
     * 确定事件是否具有被视为主要事件的 id
     * @private
     * @param {evt} event 事件对象
     */
    _isMainEvent(evt) {
        if ((evt).isPrimary === true) {
            return true;
        }
        if ((evt).isPrimary === false) {
            console.log("false 1");
            return false;
        }
        if (evt.type === 'touchend' && (evt).touches.length === 0) {
            return true;
        }
        if ((evt).changedTouches) {
            const b = ((evt).changedTouches[0].identifier === this.mainTouchId);
            return b;
        }
        return true;
    }
    targets: Array<FabricObject> = [];
    /**
     * @private
     * 处理目标和子目标的事件触发
     * @param {TPointerEvent} e 鼠标事件
     * @param {TPointerEventNames} eventType
     */
    _handleEvent(e, eventType) {
        const target = this._target,
            targets = this.targets || [],
            options = {
                e,
                target,
                subTargets: targets,
                ...fabricUtils.getEventPoints(this, e),
                transform: this._currentTransform,
                ...(eventType === 'up:before' || eventType === 'up' ? {
                    isClick: this._isClick,
                    currentTarget: this.findTarget(e),
                    // 由前面的 `findTarget` 调用设置
                    currentSubTargets: this.targets,
                } : {}),
            };
        this.dispatchEvent({
            type: `mouse${eventType} `,
            ...options
        });
        // 这可能比我们想要处理的更复杂
        target && target.fire({
            type: `mouse${eventType} `,
            ...options
        });
        for (let i = 0; i < targets.length; i++) {
            //@ts-ignore
            targets[i] !== target && targets[i].fire({
                type: `mouse${eventType} `,
                ...options
            })
        }
    }
    /**
     * @returns 存在于场景中的点（与 {@link FabricObject#getCenterPoint} 存在于同一平面上）。
     * 这意味着对 {@link viewportTransform} 的更改不会更改点的值，
     * 但是，从查看者的角度来看，点已更改。
     *
     * @example
     * const viewportPoint = sendPointToPlane(
     *  this.getScenePoint(e),
     *  canvas.viewportTransform
     * );
     *
     */
    getScenePoint(e) {
        if (this._absolutePointer) {
            return this._absolutePointer;
        }
        return this.getPointer(e);
    }
    /**
     * 此方法将考虑按下的修饰键和我们即将拖动的控件，并尝试猜测变换的锚点（原点）。
     * 这应该真正属于控件的领域，我们应该删除特定的代码以用于遗留嵌入操作。
     * @TODO 这可能需要讨论/重新发现和更改/重构
     * @private
     * @param {FabricObject} target
     * @param {string} action
     * @param {boolean} altKey
     * @returns {boolean} 如果变换应居中，则返回 true
     */
    centeredScaling = true;
    centeredRotation = true;
    _shouldCenterTransform(
        target,
        action,
        modifierKeyPressed,
    ) {
        if (!target) {
            return;
        }

        let centerTransform;

        if (
            action === SCALE ||
            action === SCALE_X ||
            action === SCALE_Y ||
            action === RESIZING
        ) {
            centerTransform = this.centeredScaling || target.centeredScaling;
        } else if (action === ROTATE) {
            centerTransform = this.centeredRotation || target.centeredRotation;
        }

        return centerTransform ? !modifierKeyPressed : modifierKeyPressed;
    }
    /**
     * 给定点击的控件，确定变换的原点。
     * 这很糟糕，因为控件可以完全有自定义名称
     * 应该在 4.0 版本发布前消失
     * @private
     */
    _getOriginFromCorner(target, controlName) {
        const origin = {
            x: target.originX,
            y: target.originY,
        };

        if (!controlName) {
            return origin;
        }

        // 是左侧控件吗？
        if (['ml', 'tl', 'bl'].includes(controlName)) {
            origin.x = RIGHT;
            // 是右侧控件吗？
        } else if (['mr', 'tr', 'br'].includes(controlName)) {
            origin.x = LEFT;
        }
        // 是顶部控件吗？
        if (['tl', 'mt', 'tr'].includes(controlName)) {
            origin.y = BOTTOM;
            // 是底部控件吗？
        } else if (['bl', 'mb', 'br'].includes(controlName)) {
            origin.y = TOP;
        }
        return origin;
    }
    /**
     * @private
     * @param {Event} e 事件对象
     * @param {FabricObject} target
     * @param {boolean} [alreadySelected] 传递 true 以设置活动控件
     */
    centeredKey;
    _setupCurrentTransform(
        e,
        target,
        alreadySelected,
    ) {
        const pointer = target.group ? // 将指针变换到目标的包含坐标平面
            sendPointToPlane(
                this.getScenePoint(e),
                undefined,
                target.group.calcTransformMatrix(),
            ) :
            this.getScenePoint(e);
        const {
            key: corner = '',
            control
        } = target.getActiveControl() || {},
            actionHandler =
                alreadySelected && control ?
                    control.getActionHandler(e, target, control)?.bind(control) :
                    fabricUtils.dragHandler,
            action = fabricUtils.getActionFromCorner(alreadySelected, corner, e, target),
            altKey = e[this.centeredKey],
            origin = this._shouldCenterTransform(target, action, altKey) ?
                ({
                    x: CENTER,
                    y: CENTER
                }) :
                this._getOriginFromCorner(target, corner),
            /**
             * 相对于目标的包含坐标平面
             * 双方同意每个点
             **/
            transform = {
                target: target,
                action,
                actionHandler,
                actionPerformed: false,
                corner,
                scaleX: target.scaleX,
                scaleY: target.scaleY,
                skewX: target.skewX,
                skewY: target.skewY,
                offsetX: pointer.x - target.left,
                offsetY: pointer.y - target.top,
                originX: origin.x,
                originY: origin.y,
                ex: pointer.x,
                ey: pointer.y,
                lastX: pointer.x,
                lastY: pointer.y,
                theta: fabricUtils.degreesToRadians(target.angle),
                width: target.width,
                height: target.height,
                shiftKey: e.shiftKey,
                altKey,
                original: {
                    ...fabricUtils.saveObjectTransform(target),
                    originX: origin.x,
                    originY: origin.y,
                },
            };

        this._currentTransform = transform;

        this.dispatchEvent({
            type: 'before:transform',
            e,
            transform,
        });

    }
    /**
     * 返回画布缩放级别
     * @return {Number}
     */
    getZoom() {
        return this.viewportTransform[0];
    }
    setZoom(zoom) {
        this.viewportTransform[0] = zoom;
        this.viewportTransform[3] = zoom;
    }
    setViewPosition(x, y) {
        this.viewportTransform[4] = x;
        this.viewportTransform[5] = y;
    }
    /**
     * 检查点是否在对象选择区域内，包括填充
     * @param {FabricObject} obj 要测试的对象
     * @param {FabricObject} [pointer] 场景坐标中的点
     * @return {Boolean} 如果点包含在给定对象的区域内，则返回 true
     * @private
     */
    _pointIsInObjectSelectionArea(obj, point) {
        // getCoords 将已经处理组解嵌
        let coords = obj.getCoords();
        const viewportZoom = this.getZoom();
        const padding = obj.padding / viewportZoom;
        if (padding) {
            const [tl, tr, br, bl] = coords;
            // 对象的角度是多少？
            // 我们可以使用 getTotalAngle，但从坐标的方向来看要容易得多，
            // 因为如果出了问题，至少我们是一致的。
            const angleRadians = Math.atan2(tr.y - tl.y, tr.x - tl.x),
                cosP = fabricUtils.cos(angleRadians) * padding,
                sinP = fabricUtils.sin(angleRadians) * padding,
                cosPSinP = cosP + sinP,
                cosPMinusSinP = cosP - sinP;

            coords = [
                new Point(tl.x - cosPMinusSinP, tl.y - cosPSinP),
                new Point(tr.x + cosPSinP, tr.y - cosPMinusSinP),
                new Point(br.x + cosPMinusSinP, br.y + cosPSinP),
                new Point(bl.x - cosPSinP, bl.y + cosPMinusSinP),
            ];
            // 在填充的情况下，我们动态计算新坐标。
            // 否则我们必须为所有内容维护 2 组坐标。
            // 我们可以重新迭代存储它们。
            // 如果这很慢，目前简化很大，不影响渲染。
            // 其背后的想法是，在目标检查之外，我们不需要知道这些坐标在哪里
        }
        return Intersection.isPointInPolygon(point, coords);
    }
    /**
     * End the current transform.
     * You don't usually need to call this method unless you are interrupting a user initiated transform
     * because of some other event ( a press of key combination, or something that block the user UX )
     * @param {Event} [e] send the mouse event that generate the finalize down, so it can be used in the event
     */
    endCurrentTransform(e?: TPointerEvent) {
        const transform = this._currentTransform;
        this._finalizeCurrentTransform(e);
        if (transform && transform.target) {
            // this could probably go inside _finalizeCurrentTransform
            transform.target.isMoving = false;
        }
        this._currentTransform = null;
    }
    _hoveredTarget?: FabricObject;
    /**
     * 这应该等同于 discardActiveObject，但不触发任何选择事件（仍然可以触发对象变换事件）。没有承诺保持这种方式。
     * 这是 discardActiveObject 的功能部分。
     * @param {Event} [e] 事件（在触发 "object:deselected" 时传递）
     * @param {FabeicObject} object 下一个要设置为活动对象的对象，原因是我们正在丢弃这个对象
     * @return {Boolean} 如果活动对象已被丢弃，则返回 true
     */
    _discardActiveObject(e, object?) {
        const obj = this._activeObject;
        if (obj) {
            // onDeselect 返回 TRUE 以取消选择；
            if (obj.onDeselect({
                e,
                object
            })) {
                return false;
            }
            if (this._currentTransform && this._currentTransform.target === obj) {
                this.endCurrentTransform(e);
            }
            if (fabricUtils.isActiveSelection(obj) && obj === this._hoveredTarget) {
                this._hoveredTarget = undefined;
            }
            this._activeObject = undefined;
            return true;
        }
        return false;
    }
    /**
     * 丢弃当前活动对象并触发事件。如果函数是由 fabric 作为鼠标事件的结果调用的，则事件作为参数传递并发送到 fire 函数以进行自定义事件。
     * 当用作方法时，e 参数没有任何应用。
     * @param {event} e
     * @return {Boolean} 如果活动对象已被丢弃，则返回 true
     */
    discardActiveObject(e?: TPointerEvent) {
        const currentActives = this.getActiveObjects(),
            activeObject = this.getActiveObject();
        if (currentActives.length) {
            this.dispatchEvent({
                type: 'selection:cleared',
                e: (e ? e : null),
                deselected: [activeObject!],
            });
        }
        const discarded = this._discardActiveObject(e);
        this._fireSelectionEvents(currentActives, e);
        // this.requestRenderAll();
        return discarded;

    }
    /**
     * @private
     * @param {TPointerEvent} e 事件对象
     * @param {FabricObject} target
     */
    _shouldClearSelection(e, target) {
        const activeObjects = this.getActiveObjects(),
            activeObject = this._activeObject;

        return !!(
            !target ||
            (target &&
                activeObject &&
                activeObjects.length > 1 &&
                activeObjects.indexOf(target) === -1 &&
                activeObject !== target &&
                !this._isSelectionKeyPressed(e)) ||
            (target && !target.evented) ||
            (target && !target.selectable && activeObject && activeObject !== target)
        );
    }
    selectionKey: TOptionalModifierKey | ModifierKey[];
    /**
     * 接受一个事件并确定是否按下了选择键
     * @private
     * @param {TPointerEvent} e 事件对象
     */
    _isSelectionKeyPressed(e) {
        const sKey = this.selectionKey;
        if (!sKey) {
            return false;
        }
        if (Array.isArray(sKey)) {
            return !!sKey.find((key) => !!key && e[key] === true);
        } else {
            return e[sKey];
        }
    }
    /**
     * @private
     * 比较旧的 activeObject 与当前的 activeObject，并触发正确的事件
     * @param {FabricObject[]} oldObjects 旧的 activeObject
     * @param {TPointerEvent} e 触发选择事件的鼠标事件
     */
    _objectsToRender?: FabricObject[];
    _fireSelectionEvents(oldObjects, e) {
        let somethingChanged = false,
            invalidate = false;
        const objects = this.getActiveObjects(),
            added: Array<FabricObject> = [],
            removed: Array<FabricObject> = [];

        oldObjects.forEach((target) => {
            if (!objects.includes(target)) {
                somethingChanged = true;
                if (target) {
                    target.fire('deselected', {
                        e,
                        target,
                    });
                    removed.push(target);
                }
            }
        });

        objects.forEach((target) => {
            if (!oldObjects.includes(target)) {
                somethingChanged = true;
                if (target) {
                    target.fire('selected', {
                        e,
                        target,
                    });
                    added.push(target);
                }
            }
        });

        if (oldObjects.length > 0 && objects.length > 0) {
            invalidate = true;
            somethingChanged &&
                this.dispatchEvent({
                    type: 'selection:updated',
                    e,
                    selected: added,
                    deselected: removed
                });
        } else if (objects.length > 0) {
            invalidate = true;

            this.dispatchEvent({
                type: 'selection:created',
                e,
                selected: added,
            });

        } else if (oldObjects.length > 0) {
            invalidate = true;

            this.dispatchEvent({
                type: 'selection:cleared',
                e,
                deselected: removed,
            });
        }
        invalidate && (this._objectsToRender = undefined);
    }
    /**
 * When the option is enabled, PointerEvent is used instead of TPointerEvent.
 * @type Boolean
 * @default
 */
    enablePointerEvents: boolean;
    /**
     * 返回事件前缀 pointer 或 mouse。
     * @private
     */
    _getEventPrefix() {
        return this.enablePointerEvents ? 'pointer' : 'mouse';
    }
    /**
     * ## 处理多重选择
     * - 切换 `target` 选择（如果未选择 `target` 则选择/如果已选择则取消选择 `target`）
     * - 在未设置活动对象或活动选择下有单个活动对象时设置活动对象。
     * ---
     * - 如果活动对象是活动选择，我们将 `target` 从中添加/删除
     * - 如果不是，将活动对象和 `target` 添加到活动选择中并将其设为活动对象。
     * @private
     * @param {TPointerEvent} e 事件对象
     * @param {FabricObject} target 选择/取消选择的事件目标
     * @returns 如果发生分组则返回 true
     */
    selection: boolean = false;
    handleMultiSelection(e, target) {
        const activeObject = this._activeObject;
        const isAS = fabricUtils.isActiveSelection(activeObject);
        if (
            // 检查画布上是否存在活动对象以及用户是否按下 `selectionKey`，同时画布支持多重选择。
            !!activeObject &&
            this._isSelectionKeyPressed(e) &&
            this.selection &&
            // 此外，用户还必须点击可选择的目标。
            !!target &&
            target.selectable &&
            // 仅当它们是不同对象时才对目标和活动对象进行分组
            // 否则我们尝试找到 `ActiveSelection` 的子目标
            (activeObject !== target || isAS) &&
            // 确保 `activeObject` 和 `target` 不是彼此的祖先，如果 `activeObject` 不是 `ActiveSelection`
            // 如果是，那么我们希望将 `target` 从中删除
            (isAS ||
                (!target.isDescendantOf(activeObject) &&
                    !activeObject.isDescendantOf(target))) &&
            // 目标接受选择
            !target.onSelect({
                e
            }) &&
            // 确保我们不在控制上方
            !activeObject.getActiveControl()
        ) {
            if (isAS) {
                const prevActiveObjects = activeObject.getObjects();
                if (target === activeObject) {
                    const pointer = this.getViewportPoint(e);
                    target =
                        // 首先搜索活动对象以找到要删除的目标
                        this.searchPossibleTargets(prevActiveObjects, pointer) ||
                        // 如果未找到，请在活动选择下搜索要添加的目标
                        // `prevActiveObjects` 将被搜索，但我们已经知道它们不会被找到
                        this.searchPossibleTargets(this._objects, pointer);
                    // 如果未找到任何内容，请退出
                    if (!target || !target.selectable) {
                        return false;
                    }
                }
                if (target.group === activeObject) {
                    // `target` 是活动选择的一部分 => 将其删除
                    activeObject.remove(target);
                    this._hoveredTarget = target;
                    this._hoveredTargets = [...this.targets];
                    // 如果删除对象后只剩下一个...
                    if (activeObject.size() === 1) {
                        // 激活最后一个剩余对象
                        // 取消选择活动选择将从中删除剩余对象
                        this._setActiveObject(activeObject.item(0), e);
                    }
                } else {
                    // `target` 不是活动选择的一部分 => 将其添加
                    activeObject.multiSelectAdd(target);
                    this._hoveredTarget = activeObject;
                    this._hoveredTargets = [...this.targets];
                }
                this._fireSelectionEvents(prevActiveObjects, e);
            } else {
                (activeObject).isEditing &&
                    (activeObject).exitEditing();
                // 将活动对象和目标添加到活动选择中并将其设为活动对象
                const klass =
                    classRegistry.getClass<typeof ActiveSelection>('ActiveSelection');
                const newActiveSelection = new klass([], {
                    /**
                     * 在调用 {@link ActiveSelection#multiSelectAdd} 之前传递画布引用至关重要
                     * 因为它使用 {@link FabricObject#isInFrontOf}，该方法依赖于画布引用
                     */
                    //@ts-ignore
                    canvas: this,
                });
                newActiveSelection.multiSelectAdd(activeObject, target);
                this._hoveredTarget = newActiveSelection;
                // 问题 4115：我们是否应该在此处考虑子目标？
                // this._hoveredTargets = [];
                // this._hoveredTargets = this.targets.concat();
                this._setActiveObject(newActiveSelection, e);
                this._fireSelectionEvents([activeObject], e);
            }
            return true;
        }
        return false;
    }
    /**
     * Clears specified context of canvas element
     * @param {CanvasRenderingContext2D} ctx Context to clear
     */
    clearContext(ctx: CanvasRenderingContext2D) {
        ctx.clearRect(0, 0, this.width, this.height);
    }
    /**
     * Returns true if object is transparent at a certain location
     * Clarification: this is `is target transparent at location X or are controls there`
     * @TODO this seems dumb that we treat controls with transparency. we can find controls
     * programmatically without painting them, the cache canvas optimization is always valid
     * @param {FabricObject} target FabricObject to check
     * @param {Number} x Left coordinate in viewport space
     * @param {Number} y Top coordinate in viewport space
     * @return {Boolean}
     */
    targetFindTolerance: number = 0;
    private pixelFindContext: CanvasRenderingContext2D | undefined = undefined;
    isTargetTransparent(target: FabricObject, x: number, y: number): boolean {
        const tolerance = this.targetFindTolerance;
        const ctx = this.pixelFindContext!;
        this.clearContext(ctx);
        ctx.save();
        ctx.translate(-x + tolerance, -y + tolerance);
        //@ts-ignore
        ctx.transform(...this.viewportTransform);
        const selectionBgc = target.selectionBackgroundColor;
        target.selectionBackgroundColor = '';
        target.render(ctx);
        target.selectionBackgroundColor = selectionBgc;
        ctx.restore();
        // our canvas is square, and made around tolerance.
        // so tolerance in this case also represent the center of the canvas.
        const enhancedTolerance = Math.round(tolerance * this.getRetinaScaling());
        return fabricUtils.isTransparent(
            ctx,
            enhancedTolerance,
            enhancedTolerance,
            enhancedTolerance,
        );
    }
    /**
     * 检查点是否在对象选择条件内。包括填充区域或启用 perPixelTargetFind 时的像素
     * @param {FabricObject} obj 要测试的对象
     * @param {FabricObject} [pointer] 视口中的点。
     * @return {Boolean} 如果点包含在给定对象的区域内，则返回 true
     * @private
     */
    // target find config
    perPixelTargetFind: boolean = false;
    _checkTarget(obj, pointer) {
        if (
            obj &&
            obj.visible &&
            obj.evented &&
            this._pointIsInObjectSelectionArea(
                obj,
                sendPointToPlane(pointer, undefined, this.viewportTransform),
            )
        ) {
            if (
                (this.perPixelTargetFind || obj.perPixelTargetFind) &&
                !(obj).isEditing
            ) {
                if (!this.isTargetTransparent(obj, pointer.x, pointer.y)) {
                    return true;
                }
            } else {
                return true;
            }
        }
        return false;
    }
    /**
     * 内部函数，用于在对象中搜索包含指针的边界框或在绘制时包含 pointerOnCanvas 的对象
     * @param {Array} [objects] 要查找的对象数组
     * @param {FabricObject} [pointer] 我们要检查的点坐标的 x,y 对象。
     * @return {FabricObject} **给定 `objects` 中的最上面的对象**，它包含指针
     * @private
     */
    _searchPossibleTargets(
        objects,
        pointer,
    ) {
        // 缓存所有其边界框包含点的目标。
        let i = objects.length;
        // 不要检查当前分组的对象，因为我们检查父组本身。
        // 直到我们专门调用此函数以在活动组中搜索
        while (i--) {
            const target = objects[i];
            if (this._checkTarget(target, pointer)) {
                if (fabricUtils.isCollection(target) && target.subTargetCheck) {
                    const subTarget = this._searchPossibleTargets(
                        target._objects,
                        pointer,
                    );
                    subTarget && this.targets.push(subTarget);
                }
                return target;
            }
        }
    }
    /**
     * 函数，用于在对象中搜索包含指针的边界框或在绘制时包含 pointerOnCanvas 的对象
     * @see {@link _searchPossibleTargets}
     * @param {FabricObject[]} [objects] 要查找的对象数组
     * @param {Point} [pointer] 要检查的视口坐标。
     * @return {FabricObject} **屏幕上最上面的对象**，它包含指针
     */
    searchPossibleTargets(objects, pointer) {
        const target = this._searchPossibleTargets(objects, pointer);

        // 如果我们在 this.targets 中找到了某些内容，并且组是交互式的，则返回仍然交互的最内层子目标
        // 重新验证为什么是交互式的。目标应该始终返回，但仅在交互时选择。
        if (
            target &&
            fabricUtils.isCollection(target) &&
            target.interactive &&
            this.targets[0]
        ) {
            /** targets[0] 是最内层嵌套的目标，但它可能在非交互式组内，因此不是选择目标 */
            const targets = this.targets;
            for (let i = targets.length - 1; i > 0; i--) {
                const t = targets[i];
                //@ts-ignore 
                if (!(fabricUtils.isCollection(t) && t.interactive)) {
                    // 其中一个子目标不是交互式的。那是我们可以返回的最后一个子目标。
                    // 我们不能更深入地挖掘；
                    return t;
                }
            }
            return targets[0];
        }

        return target;
    }
    /**
     * 确定我们点击的对象的方法
     * 11/09/2018 如果 findTarget 可以区分是完整目标还是角落外部，那就太好了。
     * @param {Event} e 鼠标事件
     * @return {FabricObject | null} 找到的目标
     */
    skipTargetFind: boolean = false;
    preserveObjectStacking: boolean = true;
    altSelectionKey: TOptionalModifierKey = undefined;
    findTarget(e) {
        if (this.skipTargetFind) {
            return undefined;
        }

        const pointer = this.getViewportPoint(e),
            activeObject = this._activeObject,
            aObjects = this.getActiveObjects();

        this.targets = [];

        if (activeObject && aObjects.length >= 1) {
            if (activeObject.findControl(pointer, fabricUtils.isTouchEvent(e))) {
                // 如果我们点击了活动对象的角落，让我们返回它。
                return activeObject;
            } else if (
                aObjects.length > 1 &&
                // 检查指针是否在活动选择上，并可能执行 `subTargetCheck`
                this.searchPossibleTargets([activeObject], pointer)
            ) {
                // 活动选择不会像普通组一样选择子目标
                return activeObject;
            } else if (
                activeObject === this.searchPossibleTargets([activeObject], pointer)
            ) {
                // 活动对象不是活动选择
                if (!this.preserveObjectStacking) {
                    return activeObject;
                } else {
                    const subTargets = this.targets;
                    this.targets = [];
                    const target = this.searchPossibleTargets(this._objects, pointer);
                    if (
                        this.altSelectionKey &&
                        e[this.altSelectionKey] &&
                        target &&
                        target !== activeObject
                    ) {
                        // alt 选择：选择活动对象，即使它不是最上面的目标
                        // 恢复目标
                        this.targets = subTargets;
                        return activeObject;
                    }
                    return target;
                }
            }
        }

        return this.searchPossibleTargets(this._objects, pointer);
    }
    /**
     * 返回事件的 id。
     * 返回 pointerId 或 identifier 或 0 表示鼠标事件
     * @private
     * @param {Event} evt 事件对象
     */
    getPointerId(evt) {
        const changedTouches = (evt).changedTouches;
        if (changedTouches) {
            return changedTouches[0] && changedTouches[0].identifier;
        }
        if (this.enablePointerEvents) {
            return (evt).pointerId;
        }
        return -1;
    }

    /**
     * 重置缓存的常用信息，在事件处理期间需要
     * @private
     */
    _resetTransformEventData() {
        this._target = this._pointer = this._absolutePointer = undefined;
    }
    getViewportPoint(e) {
        if (this._pointer) {
            return this._pointer;
        }
        return this.getPointer(e, true);
    }

    /**
     * 缓存常用信息，在事件处理期间需要
     * @private
     * @param {Event} e 事件触发的事件对象
     */
    _cacheTransformEventData(e) {
        // 重置以避免缓存过时
        this._resetTransformEventData();
        this._pointer = this.getViewportPoint(e);
        this._absolutePointer = sendPointToPlane(
            this._pointer,
            undefined,
            this.viewportTransform,
        );
        this._target = this._currentTransform ?
            this._currentTransform.target :
            this.findTarget(e);
    }

    /**
     * 定义鼠标在画布上单击时的操作的方法。
     * 该方法初始化 currentTransform 参数并渲染所有画布，以便当前图像可以放置在顶部画布上，其余的放置在容器画布上。
     * @private
     * @param {Event} e mousedown 事件触发的事件对象
     */
    fireMiddleClick: boolean;
    fireRightClick: boolean;
    protected _groupSelector: {
        x: number;
        y: number;
        deltaX: number;
        deltaY: number;
    } | null = null;
    __onMouseDown = (e) => {
        this._isClick = true;
        this._cacheTransformEventData(e);
        this._handleEvent(e, 'down:before');

        let target = this._target;

        // 如果是右键/中键单击，只需触发事件
        const {
            button
        } = e;
        if (button) {
            ((this.fireMiddleClick && button === 1) ||
                (this.fireRightClick && button === 2)) &&
                this._handleEvent(e, 'down');
            this._resetTransformEventData();
            return;
        }

        // if (this.isDrawingMode) {
        //     this._onMouseDownInDrawingMode(e);
        //     return;
        // }

        if (!this._isMainEvent(e)) {
            return;
        }

        // 如果此时某个对象正在变换，请忽略
        // 注释掉这句,避免下次点击时，无法选中对象
        if (this._currentTransform) {
            return;
        }

        let shouldRender = this._shouldRender(target);

        let grouped = false;
        if (this.handleMultiSelection(e, target)) {
            // 活动对象可能在分组时已更改
            target = this._activeObject;
            grouped = true;
            shouldRender = true;
        } else if (this._shouldClearSelection(e, target)) {
            // this.discardActiveObject(e);
        }
        // 如果启用了选择并且没有目标，或者满足以下三个条件：
        // 目标不可选择（否则我们选择它）
        // 目标未编辑
        // 目标未被选中（否则我们拖动）
        if (
            this.selection &&
            (!target ||
                (!target.selectable &&
                    !(target).isEditing &&
                    target !== this._activeObject))
        ) {
            const p = this.getScenePoint(e);
            this._groupSelector = {
                x: p.x,
                y: p.y,
                deltaY: 0,
                deltaX: 0,
            };
        }

        if (target) {
            const alreadySelected = target === this._activeObject;
            if (target.selectable && target.activeOn === 'down') {
                this.setActiveObject(target, e);
            }
            const handle = target.findControl(
                this.getViewportPoint(e),
                fabricUtils.isTouchEvent(e),
            );
            if (target === this._activeObject && (handle || !grouped)) {
                this._setupCurrentTransform(e, target, alreadySelected);
                const control = handle ? handle.control : undefined,
                    pointer = this.getScenePoint(e),
                    mouseDownHandler =
                        control && control.getMouseDownHandler(e, target, control);
                mouseDownHandler &&
                    mouseDownHandler.call(
                        control,
                        e,
                        this._currentTransform,
                        pointer.x,
                        pointer.y,
                    );
            }
        } else if (this._activeObject) {
            this._setupCurrentTransform(e, this._activeObject, true);
        }
        // 我们清除 `_objectsToRender` 以防顺序发生变化，以便在渲染时重新填充它
        // 在触发 `down` 事件之前运行，以便开发人员有机会自己填充它
        shouldRender && (this._objectsToRender = undefined);
        this._handleEvent(e, 'down');
        // 我们必须渲染所有内容，以便更新视觉效果
        shouldRender && this.requestRenderAll();
    }

    allowTouchScrolling: boolean;
    isDrawingMode: boolean;
    touchStartTime = new Date().valueOf();

    /**
     * @param {Event} e mousedown 事件触发的事件对象
     */
    onTouchStart(e) {
        this.touchStartTime = new Date().valueOf();
        // 如果未启用 allowTouchScrolling，我们将阻止滚动
        let shouldPreventScrolling = !this.allowTouchScrolling;
        const currentActiveObject = this._activeObject;
        if (this.mainTouchId === undefined) {
            this.mainTouchId = this.getPointerId(e);
        }
        this.__onMouseDown(e);
        // 执行 fabric 的 mousedown 逻辑后，让我们看看
        // 如果我们没有更改目标或我们正在绘图
        // 我们仍然希望阻止滚动
        if (
            this.isDrawingMode ||
            (currentActiveObject && this._target === currentActiveObject)
        ) {
            shouldPreventScrolling = true;
        }
        // 阻止默认行为，将从开始阻止滚动
        shouldPreventScrolling && e.preventDefault();
        this._resetTransformEventData();
        // const canvasElement = this.upperCanvasEl,
        //     eventTypePrefix = this._getEventPrefix();
        // const doc = getDocumentFromElement(canvasElement);
        // addListener(
        //     doc,
        //     'touchend',
        //     this._onTouchEnd,
        //     addEventOptions,
        // );
        // 如果我们滚动，不注册 touch move 事件
        // shouldPreventScrolling &&
        //     addListener(
        //         doc,
        //         'touchmove',
        //         this._onMouseMove,
        //         addEventOptions,
        //     );
        // 解绑 mousedown 以防止触摸设备的双重触发
        // removeListener(
        //     canvasElement,
        //     `${ eventTypePrefix } down`,
        //     this._onMouseDown,
        // );
    }
    /**
     * @private
     * @param {Event} e 发送生成最终向下的鼠标事件，以便可以在事件中使用
     */
    _finalizeCurrentTransform(e) {
        const transform = this._currentTransform,
            target = transform.target,
            options = {
                e,
                target,
                transform,
                action: transform.action,
            };

        if (target._scaling) {
            target._scaling = false;
        }

        target.setCoords();

        if (transform.actionPerformed) {
            this.dispatchEvent({
                type: 'object:modified',
                ...options
            })
            target.fire(MODIFIED, options);
        }
    }

    /**
     * 给定一个边界框，返回集合中包含在边界框中的所有对象。
     * 如果 `includeIntersecting` 为 true，则还返回与边界框相交的对象。
     * 这主要用于选择操作，不是通用方法。
     * @param {TBBox} bbox 场景坐标中的边界框
     * @param {{ includeIntersecting?: boolean }} options 包含 includeIntersecting 的对象
     * @returns 包含在边界框中的对象数组，按从上到下的堆叠顺序排列
     */
    collectObjects(
        { left, top, width, height }: TBBox,
        { includeIntersecting = true }: { includeIntersecting?: boolean } = {},
    ) {
        const objects: InteractiveFabricObject[] = [],
            tl = new Point(left, top),
            br = tl.add(new Point(width, height));

        // 我们以相反的顺序迭代，以便在点击时优先收集顶部对象。
        for (let i = this._objects.length - 1; i >= 0; i--) {
            const object = this._objects[i] as unknown as InteractiveFabricObject;
            if (
                object.selectable &&
                object.visible &&
                ((includeIntersecting && object.intersectsWithRect(tl, br)) ||
                    object.isContainedWithinRect(tl, br) ||
                    (includeIntersecting && object.containsPoint(tl)) ||
                    (includeIntersecting && object.containsPoint(br)))
            ) {
                objects.push(object);
            }
        }

        return objects;
    }
    /**
     * ## 处理选择
     * - 选择包含在选择边界框中的对象（可能相交）
     * - 设置活动对象
     * ---
     * 在鼠标移动后在鼠标松开时运行
     */
    selectionFullyContained: boolean;
    handleSelection(e) {
        if (!this.selection || !this._groupSelector) {
            return false;
        }
        const {
            x,
            y,
            deltaX,
            deltaY
        } = this._groupSelector,
            point1 = new Point(x, y),
            point2 = point1.add(new Point(deltaX, deltaY)),
            tl = point1.min(point2),
            br = point1.max(point2),
            size = br.subtract(tl);

        const collectedObjects = this.collectObjects({
            left: tl.x,
            top: tl.y,
            width: size.x,
            height: size.y,
        }, {
            includeIntersecting: !this.selectionFullyContained
        },);

        const objects =
            // 尽管此方法仅在鼠标移动后运行，但指针可以在与鼠标按下相同的位置上进行鼠标松开
            // 是否应按原样处理？
            point1.eq(point2) ?
                collectedObjects[0] ? [collectedObjects[0]] : [] :
                collectedObjects.length > 1 ?
                    collectedObjects
                        .filter((object) => !object.onSelect({
                            e
                        }))
                        .reverse() : // `setActiveObject` 将在这种情况下调用 `onSelect(collectedObjects[0])`
                    collectedObjects;

        // 设置活动对象
        if (objects.length === 1) {
            // 设置为活动对象
            this.setActiveObject(objects[0], e);
        } else if (objects.length > 1) {
            // 添加到活动选择并将其设为活动对象
            const klass = classRegistry.getClass('ActiveSelection') as any;
            this.setActiveObject(new klass(objects, {
                canvas: this
            }), e);
        }

        // 清理
        this._groupSelector = null;
        return true;
    }
    /**
     * 定义鼠标在画布上释放时的操作的方法。
     * 该方法重置 currentTransform 参数，将图像角位置存储在图像对象中，并在顶部渲染画布。
     * @private
     * @param {Event} e mouseup 事件触发的事件对象
     */
    __onMouseUp(e) {

        this._cacheTransformEventData(e);
        this._handleEvent(e, 'up:before');

        const transform = this._currentTransform;
        const isClick = this._isClick;
        const target = this._target;

        // 如果是右键/中键单击，只需触发事件并返回
        // target undefined 将使 _handleEvent 搜索目标
        const {
            button
        } = e;
        if (button) {
            ((this.fireMiddleClick && button === 1) ||
                (this.fireRightClick && button === 2)) &&
                this._handleEvent(e, 'up');
            this._resetTransformEventData();
            return;
        }

        // if (this.isDrawingMode && this._isCurrentlyDrawing) {
        //     this._onMouseUpInDrawingMode(e);
        //     return;
        // }

        if (!this._isMainEvent(e)) {
            return;
        }
        let shouldRender = false;
        if (transform) {
            this._finalizeCurrentTransform(e);
            shouldRender = transform.actionPerformed;
        }
        if (!isClick) {
            const targetWasActive = target === this._activeObject;
            this.handleSelection(e);
            if (!shouldRender) {
                shouldRender =
                    this._shouldRender(target) ||
                    (!targetWasActive && target === this._activeObject);
            }
        }
        let pointer, corner;
        if (target) {
            const found = target.findControl(
                this.getViewportPoint(e),
                fabricUtils.isTouchEvent(e),
            );
            const {
                key,
                control
            } = found || {};
            corner = key;
            if (
                target.selectable &&
                target !== this._activeObject &&
                target.activeOn === 'up'
            ) {
                this.setActiveObject(target, e);
                shouldRender = true;
            } else if (control) {
                const mouseUpHandler = control.getMouseUpHandler(e, target, control);
                if (mouseUpHandler) {
                    pointer = this.getScenePoint(e);
                    mouseUpHandler.call(control, e, transform, pointer.x, pointer.y);
                }
            }
            target.isMoving = false;
        }
        // 如果我们在不同的控制或新对象上结束变换
        // 从开始变换的角落触发原始的 mouse up
        if (
            transform &&
            (transform.target !== target || transform.corner !== corner)
        ) {
            const originalControl =
                transform.target && transform.target.controls[transform.corner],
                originalMouseUpHandler =
                    originalControl &&
                    originalControl.getMouseUpHandler(
                        e,
                        transform.target,
                        originalControl,
                    );
            pointer = pointer || this.getScenePoint(e);
            originalMouseUpHandler &&
                originalMouseUpHandler.call(
                    originalControl,
                    e,
                    transform,
                    pointer.x,
                    pointer.y,
                );
        }
        this._handleEvent(e, 'up');
        this._groupSelector = null;
        this._currentTransform = null;
        // 重置目标信息，关于哪个角被选中
        target && (target.__corner = undefined);
        if (shouldRender) {
            this.requestRenderAll();
        } else if (!isClick && !(this._activeObject)?.isEditing) {
            // this.renderTop();
        }
    }
    /**
     * 管理画布上 fabric 对象的合成 in/out 事件
     * @param {Fabric.FabricObject} target 目标，来自支持事件的目标
     * @param {FabricObject} data 事件触发的事件对象
     * @param {FabricObject} config 函数工作的配置
     * @param {String} config.targetName 画布上存储旧目标的属性
     * @param {String} [config.canvasEvtOut] 在画布级别触发的 out 事件的名称
     * @param {String} config.evtOut out 事件的名称
     * @param {String} [config.canvasEvtIn] 在画布级别触发的 in 事件的名称
     * @param {String} config.evtIn in 事件的名称
     * @private
     */
    fireSyntheticInOutEvents(
        type, {
            target,
            oldTarget,
            fireCanvas,
            e,
            ...data
        },
    ) {
        const {
            targetIn,
            targetOut,
            canvasIn,
            canvasOut
        } =
            fabricUtils.syntheticEventConfig[type];
        const targetChanged = oldTarget !== target;

        if (oldTarget && targetChanged) {
            const outOpt = {
                ...data,
                e,
                target: oldTarget,
                nextTarget: target,
                ...fabricUtils.getEventPoints(this, e),
            };

            fireCanvas && this.dispatchEvent({ type: canvasOut, ...outOpt });
            oldTarget.fire(targetOut, outOpt);
        }
        if (target && targetChanged) {
            const inOpt = {
                ...data,
                e,
                target,
                previousTarget: oldTarget,
                ...fabricUtils.getEventPoints(this, e),
            };
            fireCanvas && this.dispatchEvent({
                type: canvasIn,
                ...inOpt
            });
            target.fire(targetIn, inOpt);
        }
    }

    /**
     * @private
     */
    _performTransformAction(e, transform, pointer,) {
        const {
            action,
            actionHandler,
            target
        } = transform;

        const actionPerformed = !!actionHandler && actionHandler(e, transform, pointer.x, pointer.y);
        actionPerformed && target.setCoords();

        // 该对象可能是从控制处理程序中的函数创建的
        if (action === 'drag' && actionPerformed) {
            transform.target.isMoving = true;
            // this.setCursor(transform.target.moveCursor || this.moveCursor);
        }
        transform.actionPerformed = transform.actionPerformed || actionPerformed;
    }

    /**
     * 管理画布上 fabric 对象的 mouseout 和 mouseover 事件
     * @param {Fabric.FabricObject} target 目标，来自 mousemove 事件的目标
     * @param {Event} e mousemove 事件触发的事件对象
     * @private
     */
    _fireOverOutEvents(e, target) {
        const _hoveredTarget = this._hoveredTarget,
            _hoveredTargets = this._hoveredTargets,
            targets = this.targets,
            length = Math.max(_hoveredTargets.length, targets.length);

        this.fireSyntheticInOutEvents('mouse', {
            e,
            target,
            oldTarget: _hoveredTarget,
            fireCanvas: true,
        });
        for (let i = 0; i < length; i++) {
            this.fireSyntheticInOutEvents('mouse', {
                e,
                target: targets[i],
                oldTarget: _hoveredTargets[i],
                fireCanvas: false,
            });
        }
        this._hoveredTarget = target;
        this._hoveredTargets = this.targets.concat();
    }
    /**
     * @private
     * @param {Event} e mousemove 事件触发的事件对象
     */
    _transformObject(e) {
        const scenePoint = this.getScenePoint(e),
            transform = this._currentTransform,
            target = transform.target,
            // 将指针转换为目标的包含坐标平面
            // 指针和对象应在每个点上达成一致
            localPointer = target.group ?
                sendPointToPlane(
                    scenePoint,
                    undefined,
                    target.group.calcTransformMatrix(),
                ) :
                scenePoint;
        transform.shiftKey = e.shiftKey;
        transform.altKey = !!this.centeredKey && e[this.centeredKey];

        this._performTransformAction(e, transform, localPointer);
        transform.actionPerformed && this.requestRenderAll();
    }
    /**
     * 定义鼠标悬停在画布上时的操作的方法。
     * currentTransform 参数将定义用户是旋转/缩放/平移图像还是仅悬停。也可以进行组选择，并会取消所有其他类型的操作。
     * 在图像变换的情况下，只会渲染顶部画布。
     * @private
     * @param {Event} e mousemove 事件触发的事件对象
     */
    __onMouseMove(e) {
        this._isClick = false;
        this._cacheTransformEventData(e);
        this._handleEvent(e, 'move:before');

        // if (this.isDrawingMode) {
        //     this._onMouseMoveInDrawingMode(e);
        //     return;
        // }

        if (!this._isMainEvent(e)) {
            return;
        }

        const groupSelector = this._groupSelector;

        // 我们最初在空白区域单击，因此我们绘制一个框进行多重选择
        if (groupSelector) {
            const pointer = this.getScenePoint(e);

            groupSelector.deltaX = pointer.x - groupSelector.x;
            groupSelector.deltaY = pointer.y - groupSelector.y;

            // this.renderTop();
        } else if (!this._currentTransform) {
            const target = this.findTarget(e);
            this._fireOverOutEvents(e, target);
        } else {
            this._transformObject(e);
        }
        // this.textEditingManager.onMouseMove(e);
        this._handleEvent(e, 'move');
        this._resetTransformEventData();
    }

    /**
     * @param {Event} e mousemove 事件触发的事件对象
     */
    onMouseMove(e) {
        const activeObject = this.getActiveObject();
        !this.allowTouchScrolling &&
            (!activeObject ||
                // 拖动事件序列由活动对象在 mousedown / mousedown:before 上标记自身开始
                // 我们不能阻止事件的默认行为，以便窗口开始拖动
                !activeObject.shouldStartDragging(e)) &&
            e.preventDefault &&
            e.preventDefault();
        this.__onMouseMove(e);
    }

    // 触摸结束时如果没有选中对象是否需要取消选中
    discardWidthTouchend: boolean = false;

    /**
     * @param {Event} e mousedown 事件触发的事件对象
    */
    private _willAddMouseDown: number;
    onTouchEnd(e) {

        e.stopPropagation();
        e.preventDefault();
        if (e.touches.length > 0) {
            // 如果仍有触摸点，停止此处
            if (this._activeObject) {
                //有手指在摸，不关闭, 重置变换
                this.mainTouchId = this.getPointerId(e);
                this._resetTransformEventData();
                this.__onMouseDown(e);
            }
            return;
        }

        // 如果没有选中元素并且 已经选中的元素没有变化，那么就要取消当前选中
        if (
            (new Date().valueOf() - this.touchStartTime) < 100 &&
            !this.findTarget(e) &&
            this._currentTransform &&
            this.discardWidthTouchend
        ) {
            const { original, target } = this._currentTransform;
            if (
                parseInt(original.left) === parseInt(target.left) &&
                parseInt(original.top) === parseInt(target.top) &&
                parseInt(original.angle) === parseInt(target.angle) &&
                parseInt(original.scaleX) === parseInt(target.scaleX) &&
                parseInt(original.scaleY) === parseInt(target.scaleY)
            ) {
                this.discardActiveObject();
                this.requestRenderAll();
            }
        }


        this.__onMouseUp(e);
        this._resetTransformEventData();
        this.mainTouchId = undefined;
        const eventTypePrefix = this._getEventPrefix();
        // const doc = getDocumentFromElement(this.upperCanvasEl);
        // removeListener(
        //     doc,
        //     'touchend',
        //     this._onTouchEnd,
        //     addEventOptions,
        // );
        // removeListener(
        //     doc,
        //     'touchmove',
        //     this._onMouseMove,
        //     addEventOptions,
        // );
        if (this._willAddMouseDown) {
            clearTimeout(this._willAddMouseDown);
        }
        // this._willAddMouseDown = setTimeout(() => {
        //     // 等待 400 毫秒后重新绑定 mousedown 以防止触摸设备的双重触发
        //     addListener(
        //         this.upperCanvasEl,
        //         `${ eventTypePrefix } down`,
        //         this._onMouseDown,
        //     );
        //     this._willAddMouseDown = 0;
        // }, 400);
    }

    //渲染背景
    renderBackground(ctx = this.ctx) {
        if (!this.ctx) {
            return;
        }
        if (this.background) {
            ctx.save();
            ctx.setTransform(1, 0, 0, 1, 0, 0);
            ctx.globalCompositeOperation = "destination-over";
            ctx.fillStyle = this.background;
            ctx.fillRect(0, 0, this.width, this.height);
            ctx.restore();
        }
    }

    // 上一次渲染的时间
    preRenderTime = -30;
    rendering = false; //是否在渲染中
    /**
     * 渲染画布
     */
    renderAll(ctx = this.ctx) {
        if (!this.ctx) {
            return;
        }
        ctx.imageSmoothingEnabled = true;
        this.dispatchEvent({
            type: 'before:render',
            ctx
        });
        ctx.save();
        // 一次性应用视口变换以进行所有渲染过程
        ctx.transform(1, 0, 0, 1, 0, 0);
        ctx.clearRect(0, 0, this.width, this.height);
        for (let obj of this._objects) {
            obj.render(ctx);
        }
        ctx.restore();
        this.renderBackground(ctx);
        if (this._activeObject) {
            //重新计算对象的坐标
            this._activeObject.setCoords();
            this._activeObject._renderControls(ctx);
        }
        // if (path) {
        //     path._set('canvas', this);
        //     // 需要设置一些变量
        //     // todo 迁移到较新的版本
        //     path.shouldCache();
        //     path._transformDone = true;
        //     (path).renderCache({ forClipping: true });
        //     this.drawClipPathOnCanvas(ctx, path);
        // }
        this.dispatchEvent({
            type: 'after:render',
            ctx
        });
    }

    // 节流渲染
    async requestRenderAll(ctx = this.ctx) {
        if (!this.ctx) {
            return;
        }
        if (this.rendering) {
            return;
        }
        this.rendering = true;
        const currentTime = Date.now();
        await new Promise(resolve => setTimeout(resolve, Math.max(30 - (currentTime - this.preRenderTime), 0)));
        this.preRenderTime = Date.now();
        this.renderAll(ctx);
        this.rendering = false;
        this.preRenderTime = Date.now();
    }


    /**
     * @private
     */
    includeDefaultValues: boolean;
    _toObject(
        instance,
        methodName,
        propertiesToInclude,
    ) {
        let originalValue;

        if (!this.includeDefaultValues) {
            originalValue = instance.includeDefaultValues;
            instance.includeDefaultValues = false;
        }

        const object = instance[methodName](propertiesToInclude);
        if (!this.includeDefaultValues) {
            instance.includeDefaultValues = !!originalValue;
        }
        return object;
    }

    //清除实例的所有上下文（背景、主、顶部）
    clear(background?: `#${string}`) {

        this.remove(...this.getObjects());
        // this.backgroundImage = undefined;
        // this.overlayImage = undefined;
        if (background) {
            this.background = background;
        }
        this.dispatchEvent({ type: 'canvas:cleared' });
        this.discardActiveObject();
        // this.requestRenderAll();
        this.renderAll();
    }

    _setObject(obj) {
        for (const prop in obj) {
            this._set(prop, obj[prop]);
        }
    }
    set(key, value) {
        if (typeof key === 'object') {
            this._setObject(key);
        } else {
            this._set(key, value);
        }
        return this;
    }
    _set(key, value) {
        this[key] = value;
    }

    clipPath?: FabricObject;
    toJSON(propertiesToInclude = []) {
        const clipPath = this.clipPath;
        const clipPathData =
            clipPath && !clipPath.excludeFromExport ?
                this._toObject(clipPath, "toObject", propertiesToInclude) :
                null;

        const json = {
            version: "6.6.0",
            background: this.background,
            objects: this._objects
                .filter((object) => !object.excludeFromExport)
                .map((instance) => {
                    const data = this._toObject(instance, "toObject", propertiesToInclude);
                    // data.left = ((data.left || 0) * 2);
                    // data.top = ((data.top || 0) * 2);
                    // data.scaleX = (data.scaleX || 1) * 2;
                    // data.scaleY = (data.scaleY || 1) * 2;
                    return data;
                }),
            ...(clipPathData ? {
                clipPath: clipPathData
            } : null),
        };

        return json;
    }

    async loadFromJSON(json: FabricJSON, clear: boolean = true) {
        while (!this.canvas) {
            await new Promise(resolve => setTimeout(resolve, 30));
        }
        // 加载用到的字体
        await fontManage.loadFont(json.objects.map(o => o.fontFamily));
        const reloves = json.objects.filter(obj => Boolean(obj)).map(obj => {
            return new Promise<any>(async (resolve, reject) => {
                if (["image", 'Image'].includes(obj.type)) {
                    // 创建图片实例
                    const image = this.canvas.createImage();
                    await new Promise<void>(async (resolve) => {
                        image.onload = () => {
                            resolve();
                        }
                        image.onerror = () => {
                            console.warn(`图片加载失败:${obj.src}`);
                            reject();
                        }
                        image.src = await imgCache.get(utils.ossSuffix(obj.src, 1024));
                    });
                    const img = new FabricImg(image, {});
                    const { clipPath, ...otherObj } = obj;
                    img.set(otherObj);
                    // img.width = image.width;
                    // img.height = image.height;
                    // console.log("s", otherObj.width, image.width);
                    // img.scaleX *= (otherObj.width / image.width);
                    // img.scaleY *= (otherObj.height / image.height);
                    if (clipPath) {
                        if (["path", "Path"].includes(clipPath.type)) {
                            img.clipPath = new fabricPath("").set(clipPath);
                        } else if (["polygon", "Polygon"].includes(clipPath.type)) {
                            img.clipPath = new fabricPolygon().set(clipPath);
                        } else if (["circle", "Circle"].includes(clipPath.type)) {
                            img.clipPath = new fabricCircle().set(clipPath);
                        } else if (["rect", "Rect"].includes(clipPath.type)) {
                            img.clipPath = new fabricRact().set(clipPath);
                        }
                        if (img.clipPath) {
                            img.clipPath.set({
                                originX: 'center',
                                originY: 'center',
                            });
                        }
                        // console.log("clipPath", clipPath);
                        // console.log("_clipPath", img.clipPath);
                    }
                    resolve(img);
                } else if (['i-text', "IText", 'text'].includes(obj.type)) {

                    const { fontSize = 50, scaleX = 1, scaleY = 1, ...textObj } = obj;
                    const text = new FabricText(obj.text, {
                        ...textObj,
                        canvas: this
                    });
                    text.set({ fontSize, scaleX, scaleY });
                    text._updatePath();
                    text.setCoords();
                    resolve(text);
                } else {
                    console.log('未识别到 obj', obj);
                    throw new Error("未识别到 obj");
                }
            });
        });
        const objs = await Promise.all(reloves);
        if (clear) {
            this.clear(json.background);
        }
        this.add(...objs);
        this.renderAll();
        await new Promise(resolve => setTimeout(resolve, 100));
    }

    //导出png文件对象
    exportPNG() {
        return new Promise((resolve, reject) => {
            this.canvas.toTempFilePath({
                success: (res) => {
                    resolve(res);
                },
                fail: (err) => {
                    reject(err);
                }
            });
        });
    }

}